#include <graphics/Material.h>
#include <graphics/MaterialFileFormat.h>
#include <system/Log.h>

namespace ne
{
    Material::Material(ResourceManager *pManager,
                       const uint64_t id,
                       const std::string &name,
                       const std::string &fileName,
                       const bool isManual,
                       ManualResourceLoader *pLoader)
        : Resource(pManager, id, name, fileName, isManual, pLoader),
          mAmbient(Color(0.2f, 0.2f, 0.2f, 1.0f)),
          mDiffuse(Color(0.8f, 0.8f, 0.8f, 1.0f)),
          mSpecular(Color::ZERO),
          mEmission(Color::ZERO),
          mShininess(0.0f),
          mTransparent(false),
          mTransparentCutout(false),
          mFogEnable(false),
          mSelfIlluminated(false),
          mLightEnable(false),
          mCastShadow(false),
          mReceiveShadow(false),
          mpProgram(0)
    {
    }

    void Material::setAmbient(const Color &a)
    {
        mAmbient = a;
    }

    Color Material::getAmbient() const
    {
        return mAmbient;
    }

    void Material::setDiffuse(const Color &a)
    {
        mDiffuse = a;
    }

    Color Material::getDiffuse() const
    {
        return mDiffuse;
    }

    void Material::setSpecular(const Color &a)
    {
        mSpecular = a;
    }

    Color Material::getSpecular() const
    {
        return mSpecular;
    }

    void Material::setEmission(const Color &a)
    {
        mEmission = a;
    }

    Color Material::getEmission() const
    {
        return mEmission;
    }

    void Material::setShininess(const real a)
    {
        mShininess = a;
    }

    real Material::getShininess() const
    {
        return mShininess;
    }

    void Material::setTransparent(const bool b)
    {
        mTransparent = b;
    }

    bool Material::isTransparent() const
    {
        return mTransparent;
    }

    void Material::setTransparentCutout(const bool b)
    {
        mTransparentCutout = b;
    }

    bool Material::isTransparentCutout() const
    {
        return mTransparentCutout;
    }

    void Material::setFogEnable(const bool b)
    {
        mFogEnable = b;
    }

    bool Material::isFogEnable() const
    {
        return mFogEnable;
    }

    void Material::setSelfIlluminated(const bool b)
    {
        mSelfIlluminated = b;
    }

    bool Material::isSelfIlluminated() const
    {
        return mSelfIlluminated;
    }

    void Material::setLightEnable(const bool b)
    {
        mLightEnable = b;
    }

    bool Material::isLightEnable() const
    {
        return mLightEnable;
    }

    void Material::setCastShadow(const bool b)
    {
        mCastShadow = b;
    }

    bool Material::isCastShadow() const
    {
        return mCastShadow;
    }

    void Material::setReceiveShadow(const bool b)
    {
        mReceiveShadow = b;
    }

    bool Material::isReceiveShadow() const
    {
        return mReceiveShadow;
    }

    void Material::setProgram(const GLSLProgramPtr &pProgram)
    {
        mpProgram = pProgram;
    }

    GLSLProgramPtr Material::getProgram() const
    {
        return mpProgram;
    }

    bool Material::useProgram(const bool use)
    {
        if (mpProgram.notNull())
        {
            if (use)
            {
                mpProgram->useProgram(true);
                // TODO: set uniforms
                // TODO: set textures
                int locationTexture = 0;
                for (TextureMapConstIterator it=mTextureMap.begin(); it!=mTextureMap.end(); ++it)
                {
                    TexturePtr pTexture = it->second;
                    if (pTexture.notNull())
                    {
                        mpProgram->setUniform(it->first, locationTexture);
                        ++locationTexture;
                    }
                }
                return true;
            }
            else
            {
                mpProgram->useProgram(false);
                return true;
            }
        }
        return false;
    }

    void Material::setTexture(const std::string &name, const TexturePtr &pTexture)
    {
        mTextureMap[name] = pTexture;
    }

    bool Material::hasTexture(const std::string &name) const
    {
        TextureMapConstIterator it = mTextureMap.find(name);
        if (it != mTextureMap.end())
        {
            return true;
        }
        return false;
    }

    TexturePtr Material::getTexture(const std::string &name) const
    {
        TextureMapConstIterator it = mTextureMap.find(name);
        if (it != mTextureMap.end())
        {
            return it->second;
        }
        return TexturePtr();
    }

    bool Material::removeTexture(const std::string &name)
    {
        TextureMapIterator it = mTextureMap.find(name);
        if (it != mTextureMap.end())
        {
            mTextureMap.erase(it);
            return true;
        }
        return false;
    }

    const TextureMap& Material::getTextureMap() const
    {
        return mTextureMap;
    }

    Material::~Material()
    {
        unload();
    }

    bool Material::loadImplement()
    {
        FILE *file = openFile("rb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Open Material (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        MaterialHeader materialHeader;
        if (!fread(&materialHeader, sizeof(MaterialHeader), 1, file))
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Material (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        if (materialHeader.mMagicNum != MATERIAL_FILE_MAGIC_NUMBER ||
            materialHeader.mVersion != MATERIAL_FILE_VERSION)
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Material (%s) from file (%s) failed with wrong Magic or Version", getName().c_str(), getFileName().c_str());
            return false;
        }
        closeFile(file);

        setAmbient(Color(materialHeader.mAmbient));
        setDiffuse(Color(materialHeader.mDiffuse));
        setSpecular(Color(materialHeader.mSpecular));
        setEmission(Color(materialHeader.mEmission));
        setShininess(materialHeader.mShininess);

        setTransparent(materialHeader.mTransparent);
        setTransparentCutout(materialHeader.mTransparentCutout);
        setFogEnable(materialHeader.mFogEnable);
        setSelfIlluminated(materialHeader.mSelfIlluminated);
        setLightEnable(materialHeader.mLightEnable);
        setCastShadow(materialHeader.mCastShadow);
        setReceiveShadow(materialHeader.mReceiveShadow);

        mSize = sizeof(MaterialHeader);

        return true;
    }

    bool Material::unloadImplement()
    {
        mTextureMap.clear();
        return true;
    }

    bool Material::downloadImplement()
    {
        FILE *file = openFile("wb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Create Material (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        MaterialHeader materialHeader;
        memset(&materialHeader, 0, sizeof(MaterialHeader));

        materialHeader.mMagicNum = MATERIAL_FILE_MAGIC_NUMBER;
        materialHeader.mVersion = MATERIAL_FILE_VERSION;
        strncpy(materialHeader.mName, getFileName().c_str(), MAX_STRING_LENGTH_IN_MATERIAL_FILE);
        memcpy(materialHeader.mAmbient, getAmbient().ptr(), sizeof(real) << 2);
        memcpy(materialHeader.mDiffuse, getDiffuse().ptr(), sizeof(real) << 2);
        memcpy(materialHeader.mSpecular, getSpecular().ptr(), sizeof(real) << 2);
        memcpy(materialHeader.mEmission, getEmission().ptr(), sizeof(real) << 2);
        materialHeader.mShininess = getShininess();

        materialHeader.mTransparent = isTransparent();
        materialHeader.mTransparentCutout = isTransparentCutout();
        materialHeader.mFogEnable = isFogEnable();
        materialHeader.mSelfIlluminated = isSelfIlluminated();
        materialHeader.mLightEnable = isLightEnable();
        materialHeader.mCastShadow = isCastShadow();
        materialHeader.mReceiveShadow = isReceiveShadow();

        fwrite(&materialHeader, sizeof(MaterialHeader), 1, file);
        closeFile(file);

        return true;
    }

    size_t Material::_calculateSize() const
    {
        // FIXME: Here need to be fixed
        return mSize;
    }
}
